#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Text;

namespace Osh.Teletext
{
	public class TextEncoding
	{
		private static TextEncoding[] _encodings = new TextEncoding[128];
		private static TextEncoding[] _blockMosaicEncodings = new TextEncoding[128];
		private static TextEncoding[] _smoothMosaicEncodings = new TextEncoding[128];

		private Charset _charset;
		private bool _isMosaic;
		private bool _isBlockMosaic;
		private bool _isSmoothMosaic;
		private short[] _g0Set;
		private short[] _g2Set;
		private short[] _nationalSet;

		public TextEncoding(Charset charset)
		{
			if(charset.IsVirtual)
				throw new ArgumentOutOfRangeException("charset", "TextEncoding cannot be created for a virtual charset.");

			_charset = charset;
			_isBlockMosaic = charset.IsBlockMosaic;
			_isSmoothMosaic = charset.IsSmoothMosaic;
			_isMosaic = _isBlockMosaic || _isSmoothMosaic;
			_g0Set = charset.G0Set;
			_g2Set = charset.G2Set;
			_nationalSet = charset.NationalSet;
		}

		public char GetChar(byte charCode)
		{
			char result;
			if(!_isMosaic)
			{
				if(_nationalSet != null)
					if(TryGetNationalChar(charCode, out result))
						return result;
			}
			else
			{
				if(_isBlockMosaic)
					if((charCode >= 0x20 && charCode <= 0x3F) || (charCode >= 0x60 && charCode <= 0x7F))
						return (char)charCode;
			}
			return (char)_g0Set[charCode];
		}

		public char GetChar(byte charCode, Charset charsetModification)
		{
			if(charsetModification == Charset.Empty)
				return GetChar(charCode);
			
			if(!charsetModification.IsVirtual)
			{
				TextEncoding encoding = TextEncoding.GetEncoding(charsetModification);
				return encoding.GetChar(charCode);
			}
			else
			{
				if(charsetModification == Charset.G2)
				{
					return (char)(_g2Set != null ? (int)_g2Set[charCode] : 0x20);
				}
			}
			return GetChar(charCode);
		}

		public char GetChar(byte charCode, byte diacriticMark)
		{
			if(charCode == 0x2A)
				return '@';

			char ch;
			if(Charset.TryGetDiacriticalChar((char)charCode, diacriticMark, out ch))
				return ch;

			return GetChar(charCode);
		}

		public bool SupportsNationalOptionSubset
		{
			get { return _nationalSet != null; }
		}

		public char GetNationalChar(byte charCode)
		{
			if(_nationalSet == null)
				throw new InvalidOperationException("This TextEncoding does not support national option subset characters.");

			int charIndex = GetNationalCharIndex(charCode);
			if(charIndex == -1)
				throw new ArgumentOutOfRangeException("charCode",
					string.Format("National option subset does not include character 0x{0:X}.", charCode));

			return (char)_nationalSet[charIndex];
		}

		public bool TryGetNationalChar(byte charCode, out char ch)
		{
			if(_nationalSet != null)
			{
				int charIndex = GetNationalCharIndex(charCode);
				if(charIndex != -1)
				{
					ch = (char)_nationalSet[charIndex];
					return true;
				}
			}
			ch = '\0';
			return false;
		}

		public bool IsNationalChar(byte charCode)
		{
			if(_nationalSet == null)
				return false;

			int charIndex = GetNationalCharIndex(charCode);
			return charIndex != -1;
		}

		private int GetNationalCharIndex(byte charCode)
		{
			switch(charCode)
			{
				case 0x23: return 0;
				case 0x24: return 1;
				case 0x40: return 2;
				case 0x5B: return 3;
				case 0x5C: return 4;
				case 0x5D: return 5;
				case 0x5E: return 6;
				case 0x5F: return 7;
				case 0x60: return 8;
				case 0x7B: return 9;
				case 0x7C: return 10;
				case 0x7D: return 11;
				case 0x7E: return 12;
			}
			return -1;
		}

		public static TextEncoding GetEncoding(Charset charset)
		{
			TextEncoding[] encodings = charset.IsBlockMosaic ? _blockMosaicEncodings :
				(charset.IsSmoothMosaic ? _smoothMosaicEncodings : _encodings);

			TextEncoding encoding = encodings[charset.Code];
			if(encoding != null)
				return encoding;

			lock(typeof(TextEncoding))
			{
				encoding = new TextEncoding(charset);
				encodings[charset.Code] = encoding;
			}
			return encoding;
		}
	}
}
